﻿using System;
using System.Collections.Generic;
using BitButterECS.Entity;
using BitButterECS.Event;
using BitButterECS.Event.EventArgs;

namespace BitButterECS.System
{
    public abstract class ClockSystem : BaseSystem
	{
        public ClockSystem() : base()
        {
            InitializeByAttributes();
        }

        void InitializeByAttributes()
        {
            UpdateInterval = UpdateIntervalAttribute.GetResolutionInterval(this);

            foreach (Tuple<Type, Type> t in CacheComponentAttribute.GetCacheComponents(this))
            {
                CacheComponents.Add(new Tuple<Type, Type>(t.Item1, t.Item2));
            }
        }

        protected override void RegisterEventHandlers()
        {
            base.RegisterEventHandlers();

            ClockUpdateEvent.Handler += OnClockUpdateEvent;
        }

        protected override void UnregisterEventHandlers()
        {
            base.UnregisterEventHandlers();

            ClockUpdateEvent.Handler -= OnClockUpdateEvent;
        }

        protected override void OnAttachComponentEvent(object sender, AttachComponentEventArgs e)
        {
            var entity = e.Entity;
            var component = e.AttachedComponent;

            if (!EntityCollection.Contains(entity))
            {
                if (CacheComponents.Exists(x => x.Item1 == entity.GetType() && x.Item2 == component.GetType()))
                {
                    var shouldAdd = true;

                    foreach (Tuple<Type, Type> t in CacheComponents)
                    {
                        if (t.Item1 != entity.GetType() || entity[t.Item2] == null)
                        {
                            shouldAdd = false;
                            break;
                        }
                    }

                    if (shouldAdd)
                    {
                        EntityCollection.Add(entity);
                    }
                }
            }
        }

        protected override void OnDetachComponentEvent(object sender, DetachComponentEventArgs e)
        {
            var entity = e.Entity;
            var component = e.DetachedComponent;

            if (EntityCollection.Contains(entity))
            {
                if (CacheComponents.Exists(x => x.Item1 == entity.GetType() && x.Item2 == component.GetType()))
                {
                    var shouldKeep = true;

                    foreach (Tuple<Type, Type> t in CacheComponents)
                    {
                        if (t.Item1 != entity.GetType() || entity[t.Item2] == null)
                        {
                            shouldKeep = false;
                            break;
                        }
                    }

                    if (!shouldKeep)
                    {
                        EntityCollection.Remove(entity);
                    }

                }
            }
        }

        void OnClockUpdateEvent(object sender, ClockUpdateEventArgs e)
        {
            if (IsEnabled && UpdateInterval > 0)
            {
                if (e.TimeSinceGameStart - timerStart >= UpdateInterval)
                {
                    UpdateOnClock(sender, e);
                    timerStart = e.TimeSinceGameStart;
                }
            }
        }

        protected abstract void UpdateOnClock(object sender, ClockUpdateEventArgs e);

        public float UpdateInterval
        {
            get => updateInterval;
            set
            {
                if (value >= 0)
                {
                    updateInterval = value;
                }
            }
        }
        float updateInterval = 0f;
        float timerStart = 0f;

        protected List<BaseEntity> EntityCollection => entityCollection ?? (entityCollection = new List<BaseEntity>());
        List<BaseEntity> entityCollection;

        protected List<Tuple<Type, Type>> CacheComponents => cacheComponents ?? (cacheComponents = new List<Tuple<Type, Type>>());
        List<Tuple<Type, Type>> cacheComponents;
    }
}
